package  {
	import flash.display.GradientType;
	import flash.display.GraphicsEndFill;
	import flash.display.GraphicsGradientFill;
	import flash.display.GraphicsPath;
	import flash.display.GraphicsPathWinding;
	import flash.display.GraphicsSolidFill;
	import flash.display.GraphicsStroke;
	import flash.display.IGraphicsData;
	import flash.display.IGraphicsFill;
	import flash.display.SpreadMethod;
	import flash.display.Sprite;
	import flash.geom.Matrix;
	import flash.geom.Rectangle;
	import flash.utils.ByteArray;
	//import flash.display.GraphicsEndFill;
	//import pinocchio.SimpleStorage;
	
	
	/**
	 * ...
	 * @author David Barlia
	 */
	public class VectorGraphics {		
		
		static public const TYPE_GraphicsSolidFill		:uint 	= 1;
		static public const TYPE_GraphicsGradientFill	:uint	= 2;		
		static public const TYPE_GraphicsStroke			:uint	= 3;
		static public const TYPE_GraphicsPath			:uint 	= 4;
		//static public const TYPE_GraphicsEndFill:uint 		= 5;
		//static private const TYPE_GraphicsBitmapFill:uint 	= 6;
		//static private const TYPE_GraphicsTrianglePath:uint	= 7;  NOT SUPPORTED
		
		//private var externalStorage:Boolean;		// Is only set to true if an external bitmap storage has been assigned
		//private var _bitmapStorage:SimpleStorage;
		private var _bytes								:ByteArray;
		private var _bounds								:Rectangle;
		private var _isFillBefore						:Boolean;
		
		//-------------------------------------------------------------------------------------------------------------------
		
		public function VectorGraphics() {
			
		}
		
		/**
		 * Unpack a saved byte array into its original GraphicData form.
		 * @param	bytes	A ByteArray previously generated with VectorGraphics::toBytes()
		 * @return	Returns a vector of IGraphicsData to be passed into Graphics::drawGraphicsData()
		 */
		public function readFromBytes(bytes:ByteArray):Vector.<IGraphicsData> {
			this._bytes = bytes;
			bytes.position = 0;
			
			readBounds();
			
			var output:Vector.<IGraphicsData> = new Vector.<IGraphicsData>();
			
			while (bytes.position < bytes.length) {
				//output.push(readData());
				var type:int = _bytes.readUnsignedByte();				
				
				if (type == TYPE_GraphicsSolidFill) {
					_isFillBefore = true;
					output.push( readGraphicsSolidFill() );
				}
				else if (type == TYPE_GraphicsGradientFill) {
					_isFillBefore = true;
					output.push( readGraphicsGradientFill() );
				}
				else if (type == TYPE_GraphicsStroke) {
					_isFillBefore = false;
					output.push( readGraphicsStroke() );
				}
				else if (type == TYPE_GraphicsPath)	{
					output.push( readGraphicsPath() );
					if (_isFillBefore) {
						output.push( new GraphicsEndFill() );
					}
					else {
						output.push( new GraphicsStroke() );
					}
				}
			}
			
			return output;
		}
		
		/**
		 * Pack a vector of IGraphicData into a ByteArray.  Example usage:
		 *  <code>
		 * 	var data:Vector.&lt; IGraphicsData &gt; = graphics.readGraphicsData();
		 *	var codec:VectorGraphics = new VectorGraphics();
		 *	var bytes:ByteArray = codec.toBytes(data);
		 *  </code>
		 * @param	input	A vector of IGraphicData as returned by Graphics::readGraphicsData()
		 * @param	bounds	(optional) A bounds rectangle.
		 * @return	An uncompressed ByteArray.  Call ByteArray::deflate() to further reduce size.
		 */
		public function writeToBytes(input:Vector.<IGraphicsData>, bounds:Rectangle):ByteArray {
			_bytes = new ByteArray();
			
			_bounds = bounds;
			writeBounds();
			
			// WRITE: Data list
			for each(var data:Object in input) {
				//writeData(data);
							
				if (data is GraphicsSolidFill) {
					_bytes.writeByte(TYPE_GraphicsSolidFill);
					writeGraphicsSolidFill(data as GraphicsSolidFill);
				} 
				else if (data is GraphicsGradientFill) {
					_bytes.writeByte(TYPE_GraphicsGradientFill);
					writeGraphicsGradientFill(data as GraphicsGradientFill);
				}	
				else if (data is GraphicsStroke) {
					if (GraphicsStroke(data).fill) {
						_bytes.writeByte(TYPE_GraphicsStroke);
						writeGraphicsStroke(data as GraphicsStroke);
					}
				}
				else if (data is GraphicsPath) {
					_bytes.writeByte(TYPE_GraphicsPath);
					writeGraphicsPath(data as GraphicsPath);
				}
			}
			
			return _bytes;
		}
		
		/**
		 * Convert a ByteArray, as created with VectorGraphics::toBytes() into a Sprite
		 * @param	bytes		A ByteArray, as created with VectorGraphics::toBytes()
		 * @return	Returns a Sprite, with vectors drawn into the graphics instance
		 */
		public function toSprite(bytes:ByteArray):Sprite {
			var sprite:Sprite = new Sprite();
			sprite.graphics.drawGraphicsData(readFromBytes(bytes));
			return sprite;
		}
		
		//-------------------------------------------------------------------------------------------------------------------
		
		private function writeBounds():void {
			//if (_bounds == null || _bounds.isEmpty()) {
				//bytes.writeBoolean(false);
			//} else {
				//bytes.writeBoolean(true);
				_bytes.writeFloat(_bounds.x);
				_bytes.writeFloat(_bounds.y);
				_bytes.writeFloat(_bounds.width);
				_bytes.writeFloat(_bounds.height);
			//}
		}
		
		private function readBounds():void {
			_bounds = new Rectangle();
			//if (bytes.readBoolean()) {
				_bounds.x = _bytes.readFloat();
				_bounds.y = _bytes.readFloat();
				_bounds.width = _bytes.readFloat();
				_bounds.height = _bytes.readFloat();
			//}
		}
		
		//-------------------------------------------------------------------------------------------------------------------
		
		//private function readData():IGraphicsData {
			// Read item type
			//var type:int = _bytes.readUnsignedByte();
			//
			///*if 		(type == TYPE_GraphicsBitmapFill)	return readGraphicsBitmapFill();
			//else */
			//
			//if (type == TYPE_GraphicsGradientFill){
				//return readGraphicsGradientFill();
			//}
			//else if (type == TYPE_GraphicsPath)	{
				//return readGraphicsPath();
			//}
			//else if (type == TYPE_GraphicsSolidFill){
				//return readGraphicsSolidFill();
			//}
			//else if (type == TYPE_GraphicsStroke){
				//return readGraphicsStroke();
			//}
			///*else if (type == TYPE_GraphicsEndFill){
				//return readGraphicsEndFill();
			//}*/
			//else if (type == TYPE_GraphicsTrianglePath)	return readGraphicsTrianglePath();
			//else throw new Error("Unexpected graphics data type: " + type);
			//
			//return null;
		//}
		
		//private function writeData(data:Object):void {
			///*if (data is GraphicsBitmapFill) {
				//writeGraphicsBitmapFill(data as GraphicsBitmapFill);
			//} else */
			//if (data is GraphicsGradientFill) {
				//writeGraphicsGradientFill(data as GraphicsGradientFill);
			//} 
			//else if (data is GraphicsPath) {
				//writeGraphicsPath(data as GraphicsPath);
			//} 
			//else if (data is GraphicsSolidFill) {
				//writeGraphicsSolidFill(data as GraphicsSolidFill);
			//} 
			//else if (data is GraphicsStroke) {
				//writeGraphicsStroke(data as GraphicsStroke);
			//} 
			//else if (data is GraphicsTrianglePath) {
				//writeGraphicsTrianglePath(data as GraphicsTrianglePath);
			//} 
			///*else if (data is GraphicsEndFill) {
				//writeGraphicsEndFill();
			//}*/  
			//else {
				//throw new Error("Unexpected IGraphicData type!");
			//}
		//}
		
		//-------------------------------------------------------------------------------------------------------------------
		
		/*private function writeGraphicsBitmapFill(data:GraphicsBitmapFill):void {
			
			// Write the type
			bytes.writeByte(TYPE_GraphicsBitmapFill);
			
			// Write ONLY bitmap id's here!
			var id:int = -1;
			if (_bitmapStorage == null) {
				externalStorage = false;
				_bitmapStorage = new SimpleStorage();
			} else {
				// Check for a matching bitmap already stored
				id = _bitmapStorage.matchBitmap(data.bitmapData);
			}
			
			if (id < 0) {
				// Store the bitmap
				id = _bitmapStorage.storeBitmap(null, data.bitmapData);
			}
			
			// Write the id of the stored bitmap
			bytes.writeShort(id);*/
			
/*				// Write the bitmap, and store it for any duplicate references
				// bitmapData:BitmapData
				bytes.writeShort(data.bitmapData.width);
				bytes.writeShort(data.bitmapData.height);
				bytes.writeBoolean(data.bitmapData.transparent);
				data.bitmapData.copyPixelsToByteArray(data.bitmapData.rect, bytes);
*/				
			
			// matrix:Matrix
			/*writeMatrix(data.matrix);
		}*/
		
		
		/*private function readGraphicsBitmapFill():GraphicsBitmapFill {
			if (_bitmapStorage == null) throw new Error("Matching bitmapStorage has not been supplied!");
			
			var bitmap:BitmapData = _bitmapStorage.getBitmap(bytes.readShort());
			// TODO:  Consider allowing bitmap fill textures to be scaled to match Puppet::quality -- maybe better to put this into Import
			var matrix:Matrix = readMatrix();
			return new GraphicsBitmapFill(bitmap, matrix);
		}*/
		
		//............................
		
		private function writeGraphicsSolidFill(data:GraphicsSolidFill):void {
			
			// Write the type
			
			
			// color:uint
			// alpha:Number			
			_bytes.writeUnsignedInt(data.color | (int(data.alpha * 255) << 24));
		}
		
		private function readGraphicsSolidFill():GraphicsSolidFill {
			var color:uint = _bytes.readUnsignedInt();
			return new GraphicsSolidFill(color, ((color & 0xFF000000) >>> 24) / 255);					
		}
		
		private function writeGraphicsGradientFill(data:GraphicsGradientFill):void {			
			// spreadMethod:any
			if (data.spreadMethod == SpreadMethod.PAD) {
				_bytes.writeByte(1);
			}
			else if	(data.spreadMethod == SpreadMethod.REFLECT) {
				_bytes.writeByte(2);
			}
			else{
				_bytes.writeByte(3);
			}
			
			// interpolationMethod:String = "rgb" or "linearRGB"
			_bytes.writeBoolean( data.interpolationMethod == "rgb" );
			
			// focalPointRatio:Number
			_bytes.writeFloat(data.focalPointRatio);
			
			
			
			// type:String
			if (data.type == GradientType.LINEAR) {
				_bytes.writeByte(1);
			}
			else {
				_bytes.writeByte(2);
			}
			//else throw new Error("Unexpected gradient type: " + data.type);
			
			// matrix:any
			writeMatrix(data.matrix);
			
			// colors:Array
			// alphas:Array  <-- Duplicate of this is contained in colors[]
			// ratios:Array
			_bytes.writeByte(data.colors.length);
			for (var i:int = 0; i < data.colors.length; i++) {
				//var u:uint = uint(data.colors[i]) | (uint(data.alphas[i] * 255) << 24);
				_bytes.writeUnsignedInt(data.colors[i]);
				_bytes.writeByte(data.ratios[i]);
			}			
		}
		
		
		private function readGraphicsGradientFill():GraphicsGradientFill {
			var spread:String;
			read = _bytes.readUnsignedByte();
			if		(read == 1) {
				spread = SpreadMethod.PAD;
			}
			else if (read == 2) {
				spread = SpreadMethod.REFLECT;
			}
			else{
				spread = SpreadMethod.REPEAT;
			}
			
			var interpolation:Boolean = _bytes.readBoolean();
			var focalPointRatio:Number = _bytes.readFloat();
			
			
			var read:int = _bytes.readUnsignedByte();			
			var type:String;
			if 	(read == 1) {
				type = GradientType.LINEAR;
			}
			else {
				type = GradientType.RADIAL;
			}
			//else throw new Error("Unexpected gradient type: " + read);
			
			var matrix:Matrix = readMatrix();
			
			read = _bytes.readUnsignedByte();
			var colors:Array = new Array();
			var alphas:Array = new Array();
			var ratios:Array = new Array();
			for (var i:int = 0; i < read; i++) {
				colors[i] = _bytes.readUnsignedInt();
				ratios[i] = _bytes.readUnsignedByte();
				alphas[i] = uint(((colors[i] & 0xFF000000) >>> 24)) / 255;
				//colors[i] = (colors[i] & 0xFFFFFF);
			}			
			
			return new GraphicsGradientFill(type, colors, alphas, ratios, matrix, spread, (interpolation ? "rgb" : "linearRGB"), focalPointRatio);
		}
		
		//............................
		
/*  NOT SUPPORTED --------------------------------------------------------------------------------------
		private function writeGraphicsTrianglePath(data:GraphicsTrianglePath):void {
			
			// Write the type
			bytes.writeByte(TYPE_GraphicsTrianglePath);
			
			// vertices:Vector.<Number>
			bytes.writeShort(data.vertices.length);
			for (var i:int = 0; i < data.vertices.length; i++) {
				bytes.writeFloat(data.vertices[i]);
			}
			
			// indices:Vector.<int>
			bytes.writeShort(data.indices.length);
			for (i = 0; i < data.indices.length; i++) {
				bytes.writeShort(data.indices[i]);
			}
			
			// uvtData:Vector.<Number>
			bytes.writeShort(data.uvtData.length);
			for (i = 0; i < data.uvtData.length; i++) {
				bytes.writeFloat(data.uvtData[i]);
			}
			
			// culling:String="none"
			if 		(data.culling == TriangleCulling.NONE) 		bytes.writeByte(0);
			else if (data.culling == TriangleCulling.POSITIVE) 	bytes.writeByte(1);
			else if (data.culling == TriangleCulling.NEGATIVE) 	bytes.writeByte(2);
			else throw new Error("Unexpected  culling type: " + data.culling);
		}
		
		
		private function readGraphicsTrianglePath():GraphicsTrianglePath {
			
			var read:int = bytes.readShort()
			var vertices:Vector.<Number> = new Vector.<Number>(read, true);
			for (var i:int = 0; i < read; i++) {
				vertices[i] = bytes.readFloat();
			}
			
			read = bytes.readShort()
			var indices:Vector.<int> = new Vector.<int>(read, true);
			for (i = 0; i < read; i++) {
				indices[i] = bytes.readShort();
			}
			
			read = bytes.readShort()
			var uvtData:Vector.<Number> = new Vector.<Number>(read, true);
			for (i = 0; i < read; i++) {
				uvtData[i] = bytes.readFloat();
			}
			
			var culling:String;
			read = bytes.readUnsignedByte();
			if		(read == 0)	culling = TriangleCulling.NONE;
			else if (read == 1) culling = TriangleCulling.POSITIVE;
			else if (read == 2) culling = TriangleCulling.NEGATIVE;
			else throw new Error("Unexpected culling type: " + read);
			
			return new GraphicsTrianglePath(vertices, indices, uvtData, culling);
		}
-----------------------------------------------------------------------------------------------------*/
		
		//............................
		
		private function writeGraphicsStroke(data:GraphicsStroke):void {
			// Write the type
			
			
			// thickness:Number
			_bytes.writeFloat(data.thickness);
			
			/*  ALWAYS DEFAULT VALUE -----------------------------------
			// pixelHinting:Boolean
			//bytes.writeBoolean(data.pixelHinting);  
			
			// scaleMode:String="normal"
			if 		(data.scaleMode == LineScaleMode.NONE)			bytes.writeByte(0);
			else if (data.scaleMode == LineScaleMode.HORIZONTAL)	bytes.writeByte(1);
			else if (data.scaleMode == LineScaleMode.VERTICAL)		bytes.writeByte(2);
			else if (data.scaleMode == LineScaleMode.NORMAL)		bytes.writeByte(3);
			else throw new Error("Unexpected line scale mode: " + data.scaleMode);
			
			// caps:String="none"
			if 		(data.caps == CapsStyle.NONE)		bytes.writeByte(0);
			else if (data.caps == CapsStyle.SQUARE)		bytes.writeByte(1);
			else if (data.caps == CapsStyle.ROUND)		bytes.writeByte(2);
			else throw new Error("Unexpected caps style: " + data.caps);
			
			// joints:String="round"
			if		(data.joints == JointStyle.BEVEL)	bytes.writeByte(0);
			else if	(data.joints == JointStyle.MITER)	bytes.writeByte(1);
			else if	(data.joints == JointStyle.ROUND)	bytes.writeByte(2);
			else throw new Error("Unexpected joint style: " + data.joints);
			
			// miterLimit:Number=3
			bytes.writeFloat(data.miterLimit);
			----------------------------------------------------------*/
			
			// fill:IGraphicsFill=null
			/*if (data.fill) {
				_bytes.writeBoolean(true);
				writeData(data.fill);
			} else {
				_bytes.writeBoolean(false);
			}*/
		
			writeGraphicsSolidFill(data.fill as GraphicsSolidFill);
		}
		
		private function readGraphicsStroke():GraphicsStroke {
			
			var thickness:Number = _bytes.readFloat();
			
			/* ------- ALWAYS DEFAULT VALUE -----------------------------------
			var hinting:Boolean = bytes.readBoolean();
			
			var read:int = bytes.readUnsignedByte();
			var scale:String;
			if 		(read == 0)	scale = LineScaleMode.NONE;
			else if (read == 1) scale = LineScaleMode.HORIZONTAL;
			else if (read == 2) scale = LineScaleMode.VERTICAL;
			else if (read == 3) scale = LineScaleMode.NORMAL;
			else throw new Error("Unexpected line scale mode: " + read);
			
			read = bytes.readUnsignedByte();
			var caps:String;
			if		(read == 0)	caps = CapsStyle.NONE;
			else if (read == 1) caps = CapsStyle.SQUARE;
			else if (read == 2) caps = CapsStyle.ROUND;
			else throw new Error("Unexpected caps style: " + read);
			
			read = bytes.readUnsignedByte();
			var joints:String;
			if		(read == 0) joints = JointStyle.BEVEL;
			else if (read == 1) joints = JointStyle.MITER;
			else if (read == 2) joints = JointStyle.ROUND;
			else throw new Error("Unexpected joint style: " + read);
			
			var miter:Number = bytes.readFloat();
			-------------------------------------------------------------------*/
			
			var fill:IGraphicsData = readGraphicsSolidFill();
			//if (_bytes.readBoolean()) {
				//fill = readData();
				//if (!(fill is IGraphicsFill)) throw new Error("Unexpected fill object: " + fill);
			//}
			
			//return new GraphicsStroke(thickness, hinting, scale, caps, joints, miter, fill as IGraphicsFill);
			return new GraphicsStroke(thickness, false, "normal", "round", "round", 3, fill as IGraphicsFill);
		}
		
		//............................
		

		
		
		
		
		
		
		
		//............................
		
		private function writeGraphicsPath(data:GraphicsPath):void {
			
			// Write the type
			
			
			
			
			// data:Vector.<Number>
			_bytes.writeShort(data.data.length);
			for (i = 0; i < data.data.length; i++) {
				_bytes.writeShort(int(data.data[i] * 10));	// Use half the space, with reduced precision
				//bytes.writeFloat(data.data[i]);
			}
			
			// commands:Vector.<int> (byte)
			_bytes.writeShort(data.commands.length);
			var c:uint = 0;
			var o:uint = 0;
			for (var i:int = 0; i < data.commands.length; i++) {
				
				// Commands are 1, 2 or 3.  So pack four commands into one Byte!
				o = o | ((data.commands[i] & 3) << (2 * c));
				if (++c == 4) {
					_bytes.writeByte(o);
					o = c = 0;
				}
			}
			if (c > 0) _bytes.writeByte(o);
			
			// winding:String="evenOdd"
			if	(data.winding == GraphicsPathWinding.EVEN_ODD)	{
				_bytes.writeByte(2);
			}
			else {
				_bytes.writeByte(1);
			}
			//else throw new Error("Unexpected winding mode: " + data.winding);
		}
		
		private function readGraphicsPath():GraphicsPath {
						
			read = _bytes.readShort();
			var data:Vector.<Number> = new Vector.<Number>(read, true);
			for (i = 0; i < read; i++) {
				data[i] = _bytes.readShort() / 10;
			}
			
			var read:int = _bytes.readShort();
			var c:uint = 1;
			var o:uint = 0;
			var commands:Vector.<int> = new Vector.<int>(read, true);
			for (var i:int = 0; i < read; i++) {
				if (--c == 0) {
					o = _bytes.readUnsignedByte();
					c = 4;
				}
				commands[i] = (o & 3);
				o = o >>> 2;
			}
			
			read = _bytes.readUnsignedByte();
			var winding:String;
			if 	(read == 2) {
				winding = GraphicsPathWinding.EVEN_ODD;
			}
			else {
				winding = GraphicsPathWinding.NON_ZERO;
			}
			//else throw new Error("Unexpected path winding type: " + read);
			
			return new GraphicsPath(commands, data, winding);
		}
		
		//............................
		
		/*private function writeGraphicsEndFill():void {
			// Write the type
			bytes.writeByte(TYPE_GraphicsEndFill);
		}*/
		
		/*private function readGraphicsEndFill():GraphicsEndFill {
			return new GraphicsEndFill();
		}*/
		
		//............................
		
		private function writeMatrix(data:Matrix):void {
			// (a:Number=1, b:Number=0, c:Number=0, d:Number=1, tx:Number=0, ty:Number=0)
			_bytes.writeFloat(data.a);
			_bytes.writeFloat(data.b);
			_bytes.writeFloat(data.c);
			_bytes.writeFloat(data.d);
			_bytes.writeFloat(data.tx);
			_bytes.writeFloat(data.ty);
		}
		
		private function readMatrix():Matrix {
			return new Matrix(_bytes.readFloat(), _bytes.readFloat(), _bytes.readFloat(), _bytes.readFloat(), _bytes.readFloat(), _bytes.readFloat());
		}
		
		//-------------------------------------------------------------------------------------------------------------------
		
		/*public function dispose():void {
			if (bytes) {
				bytes.clear();
				bytes = null;
			}
			if (_bitmapStorage && !externalStorage) {
				_bitmapStorage.dispose();
				_bitmapStorage = null;
			}
		}*/
		
		/*public function get bitmapFills():int {
			return (_bitmapStorage ? _bitmapStorage.count : 0);
		}*/
		
		/*public function get bitmapStorage():SimpleStorage { return _bitmapStorage; }
		public function set bitmapStorage(value:SimpleStorage):void {
			if (_bitmapStorage != null) throw new Error("bitmapStorage may not be assigned more than once, or after this VectorGraphics instance has been used for exporting to a ByteArray.");
			externalStorage = true;
			_bitmapStorage = value;
		}*/
		
		/** The bounds of the most recently processed graphic */
		public function get bounds():Rectangle { return _bounds; }
		
		
	}
}